//
//  AudioUnitUtil.hpp
//  wi-fi-party-remade-3
//
//  Created by ken on 2021/10/30.
//

#ifndef AudioUnitUtil_h
#define AudioUnitUtil_h

#import <Foundation/Foundation.h>
#import "libwifiparty/trackreceiver.hpp"
#import <ifaddrs.h>
#import <arpa/inet.h>
#import <sys/ioctl.h>
#import <net/if.h>
#import <AudioUnit/AudioUnit.h>
#import <AVFoundation/AVFoundation.h>

void check_nil(NSError* err,const char* msg){
    if(err!=nil){
        printf("%s\n", msg);
        exit(1);
    }
}
void check_int(int ret,const char* msg){
    if(ret!=0){
        perror(msg);
        exit(1);
    }
}

struct coreinfo{
    Mixer<Trackreceiver> *mixed_sound;
    Trackreceiver *music_channel;
    Channel *vocal_channel;
    AudioUnit unit;
};

struct sockaddr_in get_sendaddr(){
    int ret;
    struct ifaddrs *ifaddr;
    struct sockaddr_in r;
    ret=getifaddrs(&ifaddr);
    check_int(ret,"Failed to get ifaddrs");
    for(;ifaddr!=NULL;ifaddr=ifaddr->ifa_next){
        struct sockaddr_in *current_addr=(struct sockaddr_in*)ifaddr->ifa_addr;
        int first_segment=current_addr->sin_addr.s_addr>>24;
        if(strcmp(ifaddr->ifa_name,"en0")==0&&first_segment!=6&&first_segment!=0){
            memcpy(&r, current_addr, sizeof(struct sockaddr_in));
            r.sin_addr.s_addr|=0xFF000000;
            r.sin_len=sizeof(struct sockaddr_in);
            r.sin_family=AF_INET;
            r.sin_port=htons(PORT);
            memset(r.sin_zero,0,sizeof(r.sin_zero));
        }
    }
    freeifaddrs(ifaddr);
    return r;
}

struct sockaddr_in* get_selfaddr(){
    int ret;
    struct ifaddrs *ifaddr;
    struct sockaddr_in *r=new struct sockaddr_in;
    ret=getifaddrs(&ifaddr);
    check_int(ret,"Failed to get ifaddrs");
    for(;ifaddr!=NULL;ifaddr=ifaddr->ifa_next){
        struct sockaddr_in *current_addr=(struct sockaddr_in*)ifaddr->ifa_addr;
        int first_segment=current_addr->sin_addr.s_addr>>24;
        if(strcmp(ifaddr->ifa_name,"en0")==0&&first_segment!=6&&first_segment!=0){
            memcpy(r, current_addr, sizeof(struct sockaddr_in));
        }
    }
    freeifaddrs(ifaddr);
    return r;
}

OSStatus recordCallback(void *inRefCon,
                        AudioUnitRenderActionFlags *ioActionFlags,
                        const AudioTimeStamp *inTimeStamp,
                        UInt32 inBusNumber,
                        UInt32 inNumberFrames,
                        AudioBufferList *ioData){
    coreinfo *ci=(coreinfo*) inRefCon;
    AudioBufferList* bufferlist=new AudioBufferList;
    bufferlist->mNumberBuffers=1;
    AudioBuffer* render_buffer=&bufferlist->mBuffers[0];
    render_buffer->mNumberChannels=1;
    render_buffer->mDataByteSize=inNumberFrames*1;
    render_buffer->mData=new char[render_buffer->mDataByteSize];
    check_int(AudioUnitRender(ci->unit, ioActionFlags, inTimeStamp, inBusNumber, inNumberFrames, bufferlist),
              "Failed to render recorded audio");
    ci->vocal_channel->send(render_buffer->mData, render_buffer->mDataByteSize, 0);
    free(render_buffer->mData);
    free(bufferlist);
    return noErr;
}

OSStatus playCallback(void *inRefCon,
                      AudioUnitRenderActionFlags *ioActionFlags,
                      const AudioTimeStamp *inTimeStamp,
                      UInt32 inBusNumber,
                      UInt32 inNumberFrames,
                      AudioBufferList *ioData){
    coreinfo *ci=(coreinfo*) inRefCon;
    AudioBuffer *outputBuffer=&ioData->mBuffers[0];
    ci->mixed_sound->readSound((char*)outputBuffer->mData, outputBuffer->mDataByteSize);
    

    return noErr;
}


AudioUnit startAudioUnit(Mixer<Trackreceiver> *mixed_sound,Trackreceiver *music_channel, Channel *vocal_channel){
    int ret=0;
    AudioComponentDescription desc;
    desc.componentType=kAudioUnitType_Output;
    desc.componentSubType=kAudioUnitSubType_RemoteIO;
    desc.componentManufacturer=kAudioUnitManufacturer_Apple;
    desc.componentFlags=0;
    desc.componentFlagsMask=0;
    
    AudioComponent component;
    component=AudioComponentFindNext(nil, &desc);
    
    AudioUnit unit;
    ret=AudioComponentInstanceNew(component, &unit);
    check_int(ret,"Failed to create audio unit");
    
    AudioStreamBasicDescription format={0};
    format.mBitsPerChannel=8;
    format.mChannelsPerFrame=1;
    format.mFramesPerPacket=1;
    format.mBytesPerFrame=1;
    format.mBytesPerPacket=1;
    format.mSampleRate=44100;
    format.mFormatID=kAudioFormatLinearPCM;
    format.mFormatFlags=kLinearPCMFormatFlagIsPacked;
    
    ret=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Output, 1, &format, sizeof(format));
    check_int(ret,"Failed to set record format");
    ret=AudioUnitSetProperty(unit, kAudioUnitProperty_StreamFormat, kAudioUnitScope_Input, 0, &format, sizeof(format));
    check_int(ret,"Failed to set playback format");
    
    AURenderCallbackStruct callback_struct;
    coreinfo *ci=new struct coreinfo;
    ci->mixed_sound=mixed_sound;
    ci->music_channel=music_channel;
    ci->vocal_channel=vocal_channel;
    ci->unit=unit;
    callback_struct.inputProcRefCon=ci;
    callback_struct.inputProc=recordCallback;
    ret=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_SetInputCallback, kAudioUnitScope_Output, 1, &callback_struct, sizeof(callback_struct));
    check_int(ret,"Failed to set record callback");
    callback_struct.inputProc=playCallback;
    ret=AudioUnitSetProperty(unit, kAudioUnitProperty_SetRenderCallback, kAudioUnitScope_Input, 0, &callback_struct, sizeof(callback_struct));
    check_int(ret,"Failed to set play callback");
    
    NSError *err;
    [[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryPlayAndRecord withOptions:AVAudioSessionCategoryOptionMixWithOthers error:&err];
    check_nil(err, "Failed to set AVAudiosession category");
    [[AVAudioSession sharedInstance] setPreferredIOBufferDuration:0.005 error:&err];
    check_nil(err, "Failed to set latency");
    [[AVAudioSession sharedInstance] setActive:true error:&err];
    check_nil(err, "Failed to activate AVAudiosession");
    
    AudioUnitInitialize(unit);
    AudioOutputUnitStart(unit);
    
    return unit;
}

void startSinging(AudioUnit unit){
    int ret;
    ret=AudioOutputUnitStop(unit);
    check_int(ret, "Failed to stop audio unit");
    ret=AudioUnitUninitialize(unit);
    check_int(ret, "Failed to uninitialize audio unit");
    UInt32 flag=1;
    ret=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));
    check_int(ret, "Failed to enable recording for AudioUnit");
    ret=AudioUnitInitialize(unit);
    check_int(ret, "Failed to re-initialize audio unit");
    ret=AudioOutputUnitStart(unit);
    check_int(ret, "Failed to re-start audio unit");
}
void stopSinging(AudioUnit unit){
    int ret;
    ret=AudioOutputUnitStop(unit);
    check_int(ret, "Failed to stop audio unit");
    ret=AudioUnitUninitialize(unit);
    check_int(ret, "Failed to uninitialize audio unit");
    UInt32 flag=0;
    ret=AudioUnitSetProperty(unit, kAudioOutputUnitProperty_EnableIO, kAudioUnitScope_Input, 1, &flag, sizeof(flag));
    check_int(ret, "Failed to disable recording for AudioUnit");
    ret=AudioUnitInitialize(unit);
    check_int(ret, "Failed to re-initialize audio unit");
    ret=AudioOutputUnitStart(unit);
    check_int(ret, "Failed to re-start audio unit");
}

#endif /* AudioUnitUtil_h */
